<?php
/**
 * Author: Louis
 * Blog: https://www.found5.com
 * Email: job@found5.com
 * Created at: 2017-07-24 17:21
 */

class FamilyTree
{

    /**
     * @var array
     */
    private $_tree;

    private $_parentSign = "parent_id";


    /**
     * FamilyTree constructor.
     *
     * @param array $tree
     */
    public function __construct(array $tree)
    {
        $this->_tree = $tree;
    }

    /**
     * @return array
     */
    public function getTree()
    {
        return $this->_tree;
    }

    /**
     * @param array $tree
     * @return FamilyTree
     */
    public function setTree($tree)
    {
        $this->_tree = $tree;
        return $this;
    }

    /**
     * @return string
     */
    public function getParentSign()
    {
        return $this->_parentSign;
    }

    /**
     * @param string $parentSign
     * @return FamilyTree
     */
    public function setParentSign($parentSign)
    {
        $this->_parentSign = $parentSign;
        return $this;
    }

    /**
     * 获取某节点的所有子节点
     *
     * @param $id
     * @return array
     */
    public function getSons($id)
    {
        $sons = [];
        foreach ($this->_tree as $key => $value) {
            if ($value[$this->_parentSign] == $id) {
                $sons[] = $value;
            }
        }
        return $sons;
    }

    /**
     * 获取某节点的所有子孙节点
     *
     * @param $id
     * @param int $level
     * @return array
     */
    public function getDescendants($id, $level = 1)
    {
        $nodes = [];
        foreach ($this->_tree as $key => $value) {
            if ($value[$this->_parentSign] == $id) {
                $value['level'] = $level;
                $nodes[] = $value;
                $nodes = array_merge($nodes, $this->getDescendants($value['id'], $level + 1));
            }
        }
        return $nodes;
    }

    /**
     * 获取某节点的所有父节点
     *
     * @param $id
     * @return array
     */
    public function getParents($id)
    {
        $nodes = [];
        $tree = array_column($this->_tree, 'id');
        foreach ($tree as $key => $value) {
            if ($tree[$id][$this->_parentSign] == $value['id']) {
                $nodes[] = $value;
            }
        }
        return $nodes;
    }

    /**
     * 获取某节点的所有祖先节点
     *
     * @param $id
     * @return array
     */
    public function getAncectors($id)
    {
        $array = $this->_getAncectors($id);
        if( isset($array[0]) ) unset($array[0]);
        return $array;
    }

    /**
     * 递归获取祖先节点
     *
     * @param $id
     * @return array
     */
    private function _getAncectors($id)
    {
        $nodes = [];
        foreach ($this->_tree as $key => $value) {
            if ($value['id'] == $id) {
                $nodes[] = $value;
                if ($value[$this->_parentSign] != 0) {
                    $nodes = array_merge($nodes, $this->_getAncectors($value[$this->_parentSign]));
                }
            }
        }
        return $nodes;
    }

    /**
     * 数组重新排序 since Yii 2.0.8
     * @param $array
     * @param $key
     * @param array $groups
     * @return array
     */
    public static function arrayIndex($array, $key, $groups = [])
    {
        $result = [];
        $groups = (array) $groups;

        foreach ($array as $element) {
            $lastArray = &$result;

            foreach ($groups as $group) {
                $value = static::getValue($element, $group);
                if (!array_key_exists($value, $lastArray)) {
                    $lastArray[$value] = [];
                }
                $lastArray = &$lastArray[$value];
            }

            if ($key === null) {
                if (!empty($groups)) {
                    $lastArray[] = $element;
                }
            } else {
                $value = static::getValue($element, $key);
                if ($value !== null) {
                    if (is_float($value)) {
                        $value = self::floatToString($value);
                    }
                    $lastArray[$value] = $element;
                }
            }
            unset($lastArray);
        }

        return $result;
    }

    /**
     * @param $array
     * @param $key
     * @param null $default
     * @return |null
     */
    public static function getValue($array, $key, $default = null)
    {
        if ($key instanceof \Closure) {
            return $key($array, $default);
        }

        if (is_array($key)) {
            $lastKey = array_pop($key);
            foreach ($key as $keyPart) {
                $array = static::getValue($array, $keyPart);
            }
            $key = $lastKey;
        }

        if (is_array($array) && (isset($array[$key]) || array_key_exists($key, $array))) {
            return $array[$key];
        }

        if (($pos = strrpos($key, '.')) !== false) {
            $array = static::getValue($array, substr($key, 0, $pos), $default);
            $key = substr($key, $pos + 1);
        }

        if (is_object($array)) {
            // this is expected to fail if the property does not exist, or __get() is not implemented
            // it is not reliably possible to check whether a property is accessible beforehand
            return $array->$key;
        } elseif (is_array($array)) {
            return (isset($array[$key]) || array_key_exists($key, $array)) ? $array[$key] : $default;
        }

        return $default;
    }

    /**
     * Safely casts a float to string independent of the current locale.
     *
     * The decimal separator will always be `.`.
     * @param float|int $number a floating point number or integer.
     * @return string the string representation of the number.
     * @since 2.0.13
     */
    public static function floatToString($number)
    {
        // . and , are the only decimal separators known in ICU data,
        // so its safe to call str_replace here
        return str_replace(',', '.', (string) $number);
    }

}